home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Tech Arsenal 1
/
Tech Arsenal (Arsenal Computer).ISO
/
tek-02
/
tpkbd10.zip
/
KEYBOARD.DOC
< prev
next >
Wrap
Text File
|
1991-07-08
|
30KB
|
576 lines
A KEYBOARD INPUT UTILITY
If you are like me, you often write programs that use "special"
keys like the arrow keys, function keys, etc., but the way these
keys are implemented seems frustrating; first you have to read a
null character, then do another read, and you have keep track of
what code means what. This detracts from the readability of the
program and disrupts your flow of thought. Or maybe you'd like
to read numbers from the keyboard but not have to worry about
trapping errors in input to avoid a fatal "Invalid numeric
format" error or check to see if the number typed is in the range
you want it. Or maybe you'd like to have a better input routine
than ReadLn to input a string. This unit tackles all those
problems, and a few others to enable you to write programs that
contain clean, good-looking, error-free input.
SINGLE CHARACTER INPUT
ReadKey is sometimes OK for character input, but when it comes to
"special" keys that aren't normal ASCII characters, it's a real
pain. That is where a function in this unit called GetKey comes
in handy. GetKey's calling syntax is the same as ReadKey's, and
it returns a character like ReadKey does. However, GetKey has
some improvements over ReadKey:
- The key pressed can be ENTIRELY determined from
the character returned by the function. There is no
need to call the function again for special keys.
- Special keys can be determined by comparing them
against either constants or simple expressions, rather
than remembering meaningless codes.
- If the user presses a special key when your
program expects regular character input, the extra
keystroke that ReadKey would normally hold after the
null character is not there with GetKey, eliminating
the danger of misinterpreting an extra keystroke, so
you should use GetKey in place of ALL calls to ReadKey,
unless you make special provisions to avoid
misinterpreting the extra keystroke.
If the character returned by GetKey is less than or equal to
#127, it is not a special character and should be taken at face
value. (Note: If GetKey returns a #0, it means the user entered
a "null" character, not that there is another character waiting
to be read. A null can be entered by pressing the Control key
along with the "@" key.) However, if the character returned is
#128 or greater, it means that the user pressed a special key
and that the character should be interpreted as follows: For
all special keys except Alt plus a letter, there are constants
that you can match against the character returned to determine
the key pressed. Here is a list of these constants, along with
the keys that generate them:
F1, F2, ... , F10 - one of the ten function keys.
ShiftF1, ShiftF2, ... , ShiftF10 - one of the
ten function keys together with the Shift key.
CntlF1, CntlF2, ... , CntlF10 - one of the ten
function keys together with the Control key.
AltF1, AltF2, ... , AltF10 - one of the ten
function keys together with the Alt key.
Alt0, Alt1, ... , Alt9 - the Alt key together with
one of the number keys from the top row of the
keyboard.
UpArrow, DownArrow, LeftArrow, RightArrow - one
of the directional arrow keys.
CntlLeftArrow, CntlRightArrow - the Control key
together with the left or right arrow key.
Normally, pressing the Control key together with the
up or down arrow key does not generate a character.
Home, End_, PgUp, PgDn, Ins, Del - the corresponding
key from the numeric keypad. Note that the constant
for the "End" key ends with an underscore to avoid
conflict with the reserved word "end" in Turbo
Pascal.
CntlHome, CntlEnd, CntlPgUp, CntlPgDn - the
Control key together with the Home, End, PgUp, or
PgDn key. The Control key together with the Ins or
Del may or may not produce a keystroke. If it does,
it produces the same result as the Ins or Del key
without the Control key.
AltMinus, AltEqual - the Alt Key together with the
"-" or "=" key from the center section of the
keyboard. The key from the far right side of the
keyboard will not produce a character when pressed
together with the Alt key.
NOTE: Support for the F11 and F12 keys is not included because
reading these keys requires calling a special "Read extended
keyboard" function, which can lock up or cause other problems
with computers that do not have BIOS support for reading the
extended keyboard.
Here is a short program to demonstrate the GetKey function. It
simply moves the cursor around on the screen as the user presses
the arrow keys. The program will continue until you press the
F10 key:
Program MoveCursor;
uses Keyboard;
const
XMax=80; {Change these if your screen}
YMax=25; {size is different from 80x25}
var
X,Y:Integer;
Ch:Char;
begin
X:=WhereX; Y:=WhereY; {Read current location}
repeat
Ch:=GetKey;
case Ch of
LeftArrow : if X>1 then dec(X); {Left arrow pressed}
RightArrow : if X<Xmax then inc(X); {Right arrow pressed}
UpArrow : if Y>1 then dec(Y); {Up arrow pressed}
DownArrow : if Y<Ymax then inc(Y); {Down arrow pressed}
end;
GotoXY(X,Y); {Move to the new location}
until Ch=F10; {Check for the F10 key}
end.
The comments are not necessary, since this program is self-
documenting because of the named constants, where a program that
worked with ReadKey and raw character codes would need extensive
commenting to explain itself. Note that the routines WhereX,
WhereY, and GotoXY normally require a "Uses CRT" statement
because they reside in the CRT unit. However, the CRT unit can
cause problems in modes where the screen width is greater than 80
columns. Since this unit used these routines, they have been
implemented with BIOS calls in order to keep the unit compatible
with all video modes. Since they were already written, they have
simply been put in the interface part of the unit so other
programs can use them too. If you still want to use the routines
in the CRT unit, you can either delete the {$DEFINE NOCRT} line
from the interface part of the unit (if you have the source
code), or you can use the fully qualified names (e.g.,
crt.wherex, crt.gotoxy, etc.) in your program.
A NOTE ON CRT COMPATIBILITY (the KeyPressed function): The only
routine in the Keyboard unit that is known to have a somewhat
different effect than the corresponding routine in the CRT unit
is the KeyPressed function. This function, in addition to
checking for keys pressed, also has the side effect that if it is
called and a key is waiting and that key turns out to be a keypad
'5' while in non-numeric mode, it will be silently removed from
the buffer. This is a necessary compromise, because these keys
continue to be put in the buffer regardless of the value of the
nonnumeric variable. This would cause erroneous results, because
getkey will ignore keypad '5' keys when in non-numeric mode, to
be consistent with the way the keypad normally works, so
keypressed would be returning TRUE when actually there was no key
available. It could not simply ignore the keystrokes, because
they would still be there next time it was called, and it would
never be able to return TRUE. Eventually, the buffer could fill
with 5's if they were not removed. The only situation this
causes is that if you call this routine with nonnumeric equal to
TRUE and there is a 5 in the buffer, then you set nonnumeric to
FALSE and call it again, the 5 will not be there any more, even
though now it should be able to be read. This seemed to be the
best solution, although it is still somewhat imperfect.
Another possibility is that the Alt key has been pressed along
with one of the letters A-Z. Because the unit already has so
many constants defined in it, it would be awkward to define
constants for each key. Instead, GetKey returns a character
equal to the ASCII value of the uppercase version of the key
pressed plus a constant amount. The constant "Alt" has been
defined for use in testing this condition. The character
returned will be equal to Chr(Ord(ASCII_value)+Alt). To
illustrate this, the following short program will continually
read characters from the keyboard, and if the Alt key is pressed
along with a letter, it will print the key that was pressed.
Again, the program ends when you press the F10 key:
program AltDemo;
uses Keyboard;
const
A=ord('A');
Z=ord('Z');
var Ch:Char;
begin
repeat
Ch:=GetKey;
if (Ch>=chr(Alt+A)) and (Ch<=chr(Alt+Z)) then
writeln('You pressed Alt-',chr(ord(Ch)-Alt));
until Ch=F10;
end.
Since it is possible to enter characters by holding down the Alt
key and typing in the ASCII value of the character on the numeric
keypad, it is possible for a character greater than #127 to be
entered from the keyboard. Since GetKey returns special
characters as characters above #127, if your program does not
detect this, it may misinterpret the character as a special
character. To correct for this, GetKey sets a predefined
variable called AltTyped, which will show whether the character
was entered this way. If this variable is TRUE, the character
was entered by typing it in on the numeric keypad. If it is
FALSE, the character was entered by normal keyboard input. If
you want to handle these situations differently, or just think
your user might do something strange, you probably will want to
test this variable before you process any special keys. Also,
you are no doubt aware that the keys on the numeric keypad can be
interpreted as either cursor control keys or as numbers,
depending on the Shift and NumLock keys. You may want to treat
these as cursor control keys all the time. For example, a
drawing program in which the user draws by moving a cursor around
on the screen probably would find it useless to treat these as
number keys. You can specify whether to treat these keys
specially by setting a predefined variable called "NonNumeric"
before calling GetKey. When this variable is TRUE, GetKey treats
the keys as cursor control keys and returns appropriate values
regardless of the condition of the Shift and NumLock keys. When
it is FALSE, GetKey treats these keys normally, returning numbers
when the shift key is down or when NumLock is on. This variable
is initially FALSE by default.
NUMERIC/STRING INPUT
This unit also contains powerful input routines that are a vast
improvement over Read and ReadLn. Three of these input routines
deal with numeric input, while two deal with string input. Here
are the headings for these five routines:
procedure ReadNo(var Number:Word; LoBound,
HiBound:Word);
procedure ReadInt(var Number:Integer; LoBound,
Hibound:Integer);
procedure ReadReal(var Number:Real; LoBound,
HiBound:Real; Decimals:Byte);
procedure ReadStr(var S:String; MaxLen:Byte;
CharsToExclude:CharSet);
procedure EditStr(var S:String; MaxLen:Byte;
CharsToExclude:CharSet);
CharSet is a type defined as set of Char. Unlike ReadLn, these
procedures do not skip to the next line when the user hits return
at the end. This is because sometimes you may want the cursor to
stay on the same line after the input. You can always add a
blank WriteLn statement if you want to skip to the next line.
ReadNo, ReadInt, and ReadReal input numbers. ReadNo inputs
positive (unsigned) integers, ReadInt inputs positive or negative
(signed) integers, and ReadReal inputs real numbers. LoBound
and HiBound are the minimum and maximum values for the number to
be input. For ReadReal, the parameter Decimals is the minimum
number of decimal places you want the user to be able to enter.
The maximum width the user will be allowed to type in will be
determined by the values passed for LoBound and HiBound (and
Decimals, if appropriate). For example, if you call ReadNo with
LoBound=0 and HiBound=500, the user will be given a maximum width
of three to type in, since no more than three digits will be
possible. If you call ReadReal with LoBound=0, HiBound=500, and
Decimals=3, the user will be given a maximum width of 7 to type
in (3 for the whole number part, 1 for the decimal point, and 3
for the decimal part). In each case, the input will be
restricted to allow only characters appropriate for the type of
number you are reading. ReadNo allows only digits to be entered.
ReadInt allows only digits and a possible negative sign as the
first character. ReadReal allows only digits, a decimal point,
and a possible negative sign as the first character. It will not
allow more than one decimal point to be entered. All three
procedures will exit immediately without doing anything if the
HiBound is less than the LoBound. If the user does not enter
anything and just hits return, the numeric variable is returned
unchanged, so if you want there to be some default value for
these procedures to return, you should set the variable to that
value before calling the procedure. If the number entered is out
of the range specified, the procedure will beep and wait for
another number to be input. (One exception: if the default value
passed is out of the range specified and the user hits return to
accept the default value, it will allow it. This will be shown
to be a useful feature later.) You should make the user aware of
both the range expected and the default value. If, for some
reason, you don't want there to be any default value, you can
take advantage of the fact that the cursor stays on the same line
after input by first setting your numeric variable to an out-of-
range value and continually reading until the variable is in
range. Suppose you wanted to enter a number between 1 and 500
but there was no good default value you could use. The following
two lines will make the user enter a value that is in range:
Number:=1000; {Start with an out-of-range value}
repeat ReadNo(Number,1,500) until Number<>1000;
.cp 2
If the user enters nothing and just hits return, the cursor will
stay exactly where it is so you can immediately make another call
to ReadNo. The program would detect that the user did not enter
anything because the default value (1000) would be returned, and
it would make the user enter something before continuing.
ReadStr and EditStr input strings. In each case, MaxLen is the
maximum length you want to allow the string to be, and
CharsToExclude is a set parameter that should contain any
characters that you do not want to allow the user to enter. For
example, when prompting for a filename, you may want to exclude
control characters (below #32), blank (' '--#32), and characters
above a lowercase z. So, if you wanted to limit the name to 30
characters and exclude the characters mentioned, you would call
ReadStr or EditStr as follows:
.cp 3
ReadStr(StringVar,30,[#0..#32,#123..#255]);
- or -
EditStr(StringVar,30,[#0..#32,#123..#255]);
If you have a large range of characters you want to disallow but
a small range you want to allow, you can take advantage of the
set difference operator and pass the invalid characters as the
set of all characters minus the set of valid characters. For
example, if you wanted to use ReadStr to read a single character
command followed by a carriage return (i.e., a string of length
1), and your range of commands was a digit from '1' to '9' or 'Q'
to quit, rather than figure all the characters that were invalid,
you could make the following call:
ReadStr(Command,1,[#0..#255]-['1'..'9','Q','q']);
This says to disallow all characters except the digits 1-9 or a
capital or lowercase 'Q'.
ReadStr allows the user to start with a blank string and enter
data. It is somewhat similar to ReadLn, except for the extra
parameters for formatting. If the user enters a null (blank)
string, the string variable passed is returned unchanged. So,
before calling ReadStr, you should set your string variable to
whatever default value you want it to have if the user enters a
null string. If you don't want to set any default string in such
a case but instead want a null string returned, you should set
your string variable to a null string ('') before calling
ReadStr. EditStr allows the user to edit an already existing
string. You should set the string variable to whatever initial
value you want it to have before you call EditStr. When EditStr
is called, the initial value will be displayed, and the user will
get a chance to edit it. The string will take on exactly
whatever value the user gives it--if the user changes it to a
null string, it will be returned as a null string, regardless of
what value it had when EditStr was called. Both procedures allow
the following editing features:
ESC - Erase the entire string.
Backspace - Delete the character to the left of the
cursor and move the cursor left one space.
Left Arrow - Move the cursor left one space.
Right Arrow - Move the cursor right one space.
Up Arrow - Move the cursor up one line.
Down Arrow - Move the cursor down one line.
Home - Move the cursor to the beginning of the string.
End - Move the cursor to the end of the string.
Ins - Toggle insert/typeover mode.
Del - Delete the character under the cursor.
Cntl-Home - Delete from the cursor to the beginning of
the string.
Cntl-End - Delete from the cursor to the end of the
string.
These keys will only function if the cursor is in an appropriate
position; you cannot backspace if you are at the beginning of the
string, you cannot go forward if you are at the end, you cannot
move up or down if the string does not extend to the line below
or above the cursor, etc. The variable parameters for these
procedures call for strings of 255 characters. If you want to
call one of them with a string variable of a different length,
you will have to include a relaxed var-string directive ({$V-})
before the call. This directive tells Turbo Pascal not to worry
if the declared lengths of the formal and actual parameters
differ when a string is used as a variable parameter, but it also
means that if you try to put more characters into the string than
will fit, you run the risk of overrunning the end of the string
and overwriting data outside the string. To avoid this, never
call ReadStr or EditStr with a MaxLen greater than the declared
length of the string. If you follow this rule, you will never
have anything to worry about.
OTHER PROCEDURES IN THIS UNIT
There are three procedures in this unit that can be used to
affect the state of the CapsLock, NumLock, and ScrollLock flags.
They are SetCapsLock, SetNumLock, and SetScrollLock, and each
takes a single parameter. The line SetCapsLock(On); will turn on
CapsLock, while the line SetCapsLock(Off); will turn it off.
"On" and "Off" are simple Boolean constants defined in the unit
to be equal to TRUE and FALSE, respectively. To test the current
states of the flags, there are three Boolean functions, called
GetCapsLock, GetNumLock, and GetScrollLock. There is also a
function to test the status of the Insert flag called GetInsert.
You can test the status of the NumLock key with the following
line:
if GetNumLock=On then writeln('NumLock is on. ');
or, more compactly:
if GetNumLock then writeln('NumLock is on. ');
Some programs simply set the status flags and do not return them
to their previous states before the program ends. This can be
annoying. It is always best to test the state of the flags
before setting any of them, then return them to their previous
states as soon as possible when you finish. Except on AT-type
computers, setting these keys affects only the PC's internal
record of the state of the keys but does not affect the status
lights on the keyboard that show whether the flags are on or off.
Therefore, if you do not return the flags to their previous state
when you finish, the lights on the keyboard may become out of
synch with the actual states of the flags within the computer.
If you can avoid it, it is best not to change these settings
because the user can change the states of the flags between the
time you save them and the time you restore them, which would
still leave the keyboard lights out of synch with the PC.
However, this is somewhat less likely so if you do find it
necessary to change the flags, this is the best way to do it.
There are five Boolean functions to test the current states of
the Shift, Control, and Alt keys: LeftShiftDown, RightShiftDown,
ShiftDown, ControlDown, and AltDown. The names are self-
explanatory, except it should be noted that LeftShiftDown and
RightShiftDown each return the state of one of the shift keys,
where ShiftDown returns true if either shift key is down.
There is a procedure called FlushBuffer, which clears any typed-
ahead characters from the input buffer. Calling this procedure
immediately before doing input can eliminate mistakes caused by
the user either making a mistake trying to type ahead or
accidentally hitting two keys at the same time, causing the
second key to be read later. DOS does this when you type "erase
*.*" and you have to wait before typing "Y" or "N".
.cp 2
There is a byte-valued function called ScreenWidth, which will
return the number of columns on the screen. This function was
used by the input routines, so it was simply placed in the
interface of the unit so other programs could use it also.
Finally, there are two procedures dealing with the cursor called
ChgCursor, which takes two byte-sized parameters to specify the
starting and ending row (0 to 7) for the cursor; and GetCursor,
which returns the current starting and ending row for the cursor.
The typical thin cursor would be set by the line
ChgCursor(6,7);
which means to use the bottom two lines (6 and 7) in defining the
cursor. A block cursor can be set by the line
ChgCursor(0,7);
which means to use all the lines (0 through 7) in defining the
cursor. The cursor can be made invisible by the line
ChgCursor($20,0);
- or -
ChgCursor(32,0);
This procedure can be handy to vanish the cursor when waiting for
single keypresses or to make "block" cursors in word processor-
type programs to show that "insert mode" is on. By calling
GetCursor before calling ChgCursor, you can return the cursor to
the state it was in before you changed it.
IN CONCLUSION
The file KEYTEST.PAS demonstrates the use of the procedures
contained in this unit. First, it contains a loop to print the
states of the various toggle keys (Shift, Alt, Control, Caps
Lock, Num Lock, and Scroll Lock), which continues until a key is
pressed. Then, it requests a number of each type and displays
the value returned. Next, it asks for a string to be input,
displays the string, and allows you to edit the string. Finally,
it turns on Num Lock and requests you to press a keypad key, to
demonstrate the use of the NonNumeric variable. The interface
section of this unit is contained in the file KEYBOARD.INT so
that you can see the declarations of the constants, procedures,
and functions in the unit at a glance.
If you find this unit helpful in developing your own programs, a
donation of any size would be appreciated; however, it is not
required unless you will be using these procedures in a program
that you will be releasing to the public; in this case, a minimum
donation of $10 is requested.
Please send any donations to:
Tom Swingle
Rt. 1 Box 292
Waterford, OH 45786
My college address, to reach me quicker (until June, 1992) is:
Tom Swingle
114 Grosvenor St.
Athens, OH 45701
Whether or not you send a donation, any feedback you have on this
unit is certainly welcome, and I will try to correct any bugs
reported, although I think I have tested it thoroughly enough
that almost all the bugs are gone (I have tested it quite
extensively and used it in several programs of mine). More than
anything, I would like to hear how this unit works with unusual
setups, like computers with keystroke buffer extenders or other
such things. I will gladly answer any questions as soon as I
can. If you have access to electronic mail, you can reach me
faster at
tswingle@oucsace.cs.ohiou.edu or swingle@duce.cs.ohiou.edu.
This unit is still undergoing rigorous testing, and bug reports
(if any, hopefully there are none) will be coming in as time goes
by, so contact me to see if there have been any updates, if this
unit interests you!